/*
 * Copyright 2011-2012 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.sabayframework.mem.impl;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

import org.sabayframework.mem.BigBuffer;

/**
 * BigBuffer implementation backed by a byte array. 
 * All array access operations do not check array boundaries so invalid argument 
 * in the worst case can crash JVM. 
 * @author Alex Antonau
 * */
public final class UnsafeByteArrayBuffer extends UnsafeBigBuffer {
 
    private final byte[] buffer;
    private final int offset;
    
	public UnsafeByteArrayBuffer(byte[] buffer, int offset, int length) {
		super(0, length, length);
		this.buffer = buffer;
		this.offset = offset;
	}
	
	public UnsafeByteArrayBuffer(byte[] buffer) {
		this(buffer, 0, buffer.length);
	}
	/**
	 * Creates a duplicate of given buffer.
	 * */
	private UnsafeByteArrayBuffer(UnsafeByteArrayBuffer b) {
		super(b.position, b.limit(), b.capacity());
		this.buffer = b.buffer;
		this.offset = b.offset;
	}
	
	@Override
	public boolean isReadOnly() {
		return false;
	}

	@Override
	public boolean hasArray() {
		return true;
	}

	@Override
	public byte[] array() {
		return buffer;
	}

	@Override
	public int arrayOffset() {
		return offset;
	}

	@Override
	public boolean isDirect() {
		return false;
	}

	@Override
	public ByteOrder order() {
		return ByteOrder.nativeOrder();
	}

	@Override
	public BigBuffer slice() {
		return new UnsafeByteArrayBuffer(buffer, (int)position+offset, (int)remaining());
	}

	@Override
	public BigBuffer duplicate() {
		return new UnsafeByteArrayBuffer(this);
	}

	@Override
	public BigBuffer compact() {
		int bytesToCopy = (int)(limit()-position);
		System.arraycopy(buffer, (int)position+offset, buffer, offset, bytesToCopy);
        limit(capacity());
        position = bytesToCopy;
		return this;
	}

	@Override
	public byte get() {
		final byte value = unsafe.getByte(buffer, byteArrayOffset + offset + position);
		++position;
		return value;
	}

	@Override
	public void put(final byte b) {
		unsafe.putByte(buffer, byteArrayOffset + offset + position, b);
		++position;
	}

	@Override
	public short getShort() {
		final short value = unsafe.getShort(buffer, byteArrayOffset + offset + position);
		position += SIZE_OF_SHORT;
		return value;
	}

	@Override
	public void putShort(final short value) {
		unsafe.putShort(buffer, byteArrayOffset + offset + position, value);
		position += SIZE_OF_SHORT;
	}

	@Override
	public int getInt() {
		final int value = unsafe.getInt(buffer, byteArrayOffset + offset + position);
		position += SIZE_OF_INT;
		return value;
	}

	@Override
	public void putInt(final int value) {
		unsafe.putInt(buffer, byteArrayOffset + offset + position, value);
		position += SIZE_OF_INT;
	}

	@Override
	public long getLong() {
		final long value = unsafe.getLong(buffer, byteArrayOffset + offset + position);
		position += SIZE_OF_LONG;
        return value;
	}

	@Override
	public void putLong(final long value) {
		unsafe.putLong(buffer, byteArrayOffset + offset + position, value);
		position += SIZE_OF_LONG;
	}

	@Override
	public float getFloat() {
		final float value = unsafe.getFloat(buffer, byteArrayOffset + offset + position);
		position += SIZE_OF_FLOAT;
        return value;
	}

	@Override
	public void putFloat(final float value) {
		unsafe.putFloat(buffer, byteArrayOffset + offset + position, value);
		position += SIZE_OF_FLOAT;
	}

	@Override
	public double getDouble() {
		final double value = unsafe.getDouble(buffer, byteArrayOffset + offset + position);
		position += SIZE_OF_DOUBLE;
        return value;
	}

	@Override
	public void putDouble(final double value) {
		unsafe.putDouble(buffer, byteArrayOffset + offset + position, value);
		position += SIZE_OF_DOUBLE;
	}

	@Override
	public char getChar() {
		final char value = unsafe.getChar(buffer, byteArrayOffset + offset + position);
		position += SIZE_OF_CHAR;
        return value;
	}

	@Override
	public void putChar(final char value) {
		unsafe.putChar(buffer, byteArrayOffset + offset + position, value);
		position += SIZE_OF_CHAR;
	}

	@Override
	public void get(final byte[] dst) {
        final long bytesToCopy = dst.length;
        unsafe.copyMemory(buffer, byteArrayOffset + offset + position,
                          dst, byteArrayOffset,
                          bytesToCopy);
        position += bytesToCopy;
	}

	@Override
	public void get(final byte[] dst,final int offset,final int length) {
        final long bytesToCopy = length;
        unsafe.copyMemory(buffer, byteArrayOffset + this.offset + this.position,
                          dst, byteArrayOffset + offset,
                          bytesToCopy);
        position += bytesToCopy;
	}

	@Override
	public void put(final byte[] src) {
		final long bytesToCopy = src.length;
        unsafe.copyMemory(src, byteArrayOffset,
                          buffer, byteArrayOffset + offset + position,
                          bytesToCopy);
        position += bytesToCopy;
	}

	@Override
	public void put(final byte[] src,final int offset,final int length) {
		final long bytesToCopy = length;
        unsafe.copyMemory(src, byteArrayOffset + offset,
                          buffer, byteArrayOffset + this.offset + this.position,
                          bytesToCopy);
        position += bytesToCopy;
	}

	@Override
	public void get(final short[] dst) {
        final long bytesToCopy = dst.length * SIZE_OF_SHORT;
        unsafe.copyMemory(buffer, byteArrayOffset + offset + position,
                          dst, shortArrayOffset,
                          bytesToCopy);
        position += bytesToCopy;
	}

	@Override
	public void get(final short[] dst,final int offset,final int length) {
        final long bytesToCopy = length * SIZE_OF_SHORT;
        unsafe.copyMemory(buffer, byteArrayOffset + this.offset + this.position,
                          dst, shortArrayOffset + offset * SIZE_OF_SHORT,
                          bytesToCopy);
        position += bytesToCopy;
	}

	@Override
	public void put(final short[] src) {
		final long bytesToCopy = src.length * SIZE_OF_SHORT;
        unsafe.copyMemory(src, shortArrayOffset,
                          buffer, byteArrayOffset + offset + position,
                          bytesToCopy);
        position += bytesToCopy;
	}

	@Override
	public void put(final short[] src,final int offset,final int length) {
		final long bytesToCopy = length * SIZE_OF_SHORT;
        unsafe.copyMemory(src, shortArrayOffset + offset * SIZE_OF_SHORT,
                          buffer, byteArrayOffset + this.offset + this.position,
                          bytesToCopy);
        position += bytesToCopy;
	}

	@Override
	public void get(final char[] dst) {
        final long bytesToCopy = dst.length * SIZE_OF_CHAR;
        unsafe.copyMemory(buffer, byteArrayOffset + offset + position,
                          dst, charArrayOffset,
                          bytesToCopy);
        position += bytesToCopy;
	}

	@Override
	public void get(final char[] dst,final int offset,final int length) {
        final long bytesToCopy = length * SIZE_OF_CHAR;
        unsafe.copyMemory(buffer, byteArrayOffset + this.offset + this.position,
                          dst, charArrayOffset + offset * SIZE_OF_CHAR,
                          bytesToCopy);
        position += bytesToCopy;
	}

	@Override
	public void put(final char[] src) {
		final long bytesToCopy = src.length * SIZE_OF_CHAR;
        unsafe.copyMemory(src, charArrayOffset,
                          buffer, byteArrayOffset + offset + position,
                          bytesToCopy);
        position += bytesToCopy;
	}

	@Override
	public void put(final char[] src,final int offset,final int length) {
		final long bytesToCopy = length * SIZE_OF_CHAR;
        unsafe.copyMemory(src, charArrayOffset + offset * SIZE_OF_CHAR,
                          buffer, byteArrayOffset + this.offset + this.position,
                          bytesToCopy);
        position += bytesToCopy;
	}

	@Override
	public void get(final int[] dst) {
        final long bytesToCopy = dst.length * SIZE_OF_INT;
        unsafe.copyMemory(buffer, byteArrayOffset + offset + position,
                          dst, intArrayOffset,
                          bytesToCopy);
        position += bytesToCopy;
	}

	@Override
	public void get(final int[] dst,final int offset,final int length) {
        final long bytesToCopy = length * SIZE_OF_INT;
        unsafe.copyMemory(buffer, byteArrayOffset + this.offset + this.position,
                          dst, intArrayOffset + offset * SIZE_OF_INT,
                          bytesToCopy);
        position += bytesToCopy;
	}

	@Override
	public void put(final int[] src) {
		final long bytesToCopy = src.length * SIZE_OF_INT;
        unsafe.copyMemory(src, intArrayOffset,
                          buffer, byteArrayOffset + offset + position,
                          bytesToCopy);
        position += bytesToCopy;
	}

	@Override
	public void put(final int[] src,final int offset,final int length) {
		final long bytesToCopy = length * SIZE_OF_INT;
        unsafe.copyMemory(src, intArrayOffset + offset * SIZE_OF_INT,
                          buffer, byteArrayOffset + this.offset + this.position,
                          bytesToCopy);
        position += bytesToCopy;
	}

	@Override
	public void get(final float[] dst) {
        final long bytesToCopy = dst.length * SIZE_OF_FLOAT;
        unsafe.copyMemory(buffer, byteArrayOffset + offset + position,
                          dst, floatArrayOffset,
                          bytesToCopy);
        position += bytesToCopy;
	}

	@Override
	public void get(final float[] dst,final int offset,final int length) {
        final long bytesToCopy = length * SIZE_OF_FLOAT;
        unsafe.copyMemory(buffer, byteArrayOffset + this.offset + this.position,
                          dst, floatArrayOffset + offset * SIZE_OF_FLOAT,
                          bytesToCopy);
        position += bytesToCopy;
	}

	@Override
	public void put(final float[] src) {
		final long bytesToCopy = src.length * SIZE_OF_FLOAT;
        unsafe.copyMemory(src, floatArrayOffset,
                          buffer, byteArrayOffset + offset + position,
                          bytesToCopy);
        position += bytesToCopy;
	}

	@Override
	public void put(final float[] src,final int offset,final int length) {
		final long bytesToCopy = length * SIZE_OF_FLOAT;
        unsafe.copyMemory(src, floatArrayOffset + offset * SIZE_OF_FLOAT,
                          buffer, byteArrayOffset + this.offset + this.position,
                          bytesToCopy);
        position += bytesToCopy;
	}

	@Override
	public void get(final long[] dst) {
        final long bytesToCopy = dst.length * SIZE_OF_LONG;
        unsafe.copyMemory(buffer, byteArrayOffset + offset + position,
                          dst, longArrayOffset,
                          bytesToCopy);
        position += bytesToCopy;
	}

	@Override
	public void get(final long[] dst,final int offset,final int length) {
        final long bytesToCopy = length * SIZE_OF_LONG;
        unsafe.copyMemory(buffer, byteArrayOffset + this.offset + this.position,
                          dst, longArrayOffset + offset * SIZE_OF_LONG,
                          bytesToCopy);
        position += bytesToCopy;
	}

	@Override
	public void put(final long[] src) {
		final long bytesToCopy = src.length * SIZE_OF_LONG;
        unsafe.copyMemory(src, longArrayOffset,
                          buffer, byteArrayOffset + offset + position,
                          bytesToCopy);
        position += bytesToCopy;
	}

	@Override
	public void put(final long[] src,final int offset,final int length) {
		final long bytesToCopy = length * SIZE_OF_LONG;
        unsafe.copyMemory(src, longArrayOffset + offset * SIZE_OF_LONG,
                          buffer, byteArrayOffset + this.offset + this.position,
                          bytesToCopy);
        position += bytesToCopy;
	}

	@Override
	public void get(final double[] dst) {
        final long bytesToCopy = dst.length * SIZE_OF_DOUBLE;
        unsafe.copyMemory(buffer, byteArrayOffset + offset + position,
                          dst, doubleArrayOffset,
                          bytesToCopy);
        position += bytesToCopy;
	}

	@Override
	public void get(final double[] dst,final int offset,final int length) {
        final long bytesToCopy = length * SIZE_OF_DOUBLE;
        unsafe.copyMemory(buffer, byteArrayOffset + this.offset + this.position,
                          dst, doubleArrayOffset + offset * SIZE_OF_DOUBLE,
                          bytesToCopy);
        position += bytesToCopy;
	}

	@Override
	public void put(final double[] src) {
		final long bytesToCopy = src.length * SIZE_OF_DOUBLE;
        unsafe.copyMemory(src, doubleArrayOffset,
                          buffer, byteArrayOffset + offset + position,
                          bytesToCopy);
        position += bytesToCopy;
	}

	@Override
	public void put(double[] src, int offset, int length) {
		final long bytesToCopy = length * SIZE_OF_DOUBLE;
        unsafe.copyMemory(src, doubleArrayOffset + offset * SIZE_OF_DOUBLE,
                          buffer, byteArrayOffset + this.offset + this.position,
                          bytesToCopy);
        position += bytesToCopy;
	}

	@Override
	public byte get(final long index) {
		return unsafe.getByte(buffer, byteArrayOffset + offset + index);
	}

	@Override
	public void put(final long index,final byte value) {
		unsafe.putByte(buffer, byteArrayOffset + offset + index, value);
	}

	@Override
	public char getChar(final long index) {
		return unsafe.getChar(buffer, byteArrayOffset + offset + index);
	}

	@Override
	public void putChar(final long index,final char value) {
		unsafe.putChar(buffer, byteArrayOffset + offset + index, value);
	}

	@Override
	public short getShort(final long index) {
		return unsafe.getShort(buffer, byteArrayOffset + offset + index);
	}

	@Override
	public void putShort(final long index,final short value) {
		unsafe.putShort(buffer, byteArrayOffset + offset + index, value);
	}

	@Override
	public int getInt(final long index) {
		return unsafe.getInt(buffer, byteArrayOffset + offset + index);
	}

	@Override
	public void putInt(final long index,final int value) {
		unsafe.putInt(buffer, byteArrayOffset + offset + index, value);
	}

	@Override
	public float getFloat(final long index) {
		return unsafe.getFloat(buffer, byteArrayOffset + offset + index);
	}

	@Override
	public void putFloat(final long index,final float value) {
		unsafe.putFloat(buffer, byteArrayOffset + offset + index, value);
	}

	@Override
	public long getLong(final long index) {
		return unsafe.getLong(buffer, byteArrayOffset + offset + index);
	}

	@Override
	public void putLong(final long index,final long value) {
		unsafe.putLong(buffer, byteArrayOffset + offset + index, value);
	}

	@Override
	public double getDouble(final long index) {
		return unsafe.getDouble(buffer, byteArrayOffset + offset + index);
	}

	@Override
	public void putDouble(final long index,final double value) {
		unsafe.putDouble(buffer, byteArrayOffset + offset + index, value);
	}
	@Override
	public void put(BigBuffer src) {
		if(src instanceof ReadOnlyBigBuffer){
			//get inner buffer
			src = ((ReadOnlyBigBuffer)src).getBuffer();
		}
		if(src instanceof UnsafeDirectBuffer){
			UnsafeDirectBuffer other = (UnsafeDirectBuffer)src;
			final long bytesToCopy = other.remaining();
	        unsafe.copyMemory(null, other.getAddress() + other.position,
	        				  buffer, byteArrayOffset + offset + position,
                    		  bytesToCopy);
	        other.position += bytesToCopy;
	        position += bytesToCopy;
		} else if(src.hasArray()) {
			Object array = src.array();
			final long bytesToCopy = src.remaining();
	        unsafe.copyMemory(array, byteArrayOffset + src.arrayOffset() + src.position(),
	        				  buffer, byteArrayOffset + offset + position,
                    		  bytesToCopy);
	        src.position(bytesToCopy+src.position());
	        this.position += bytesToCopy;
		}else{
			int remaining = (int)src.remaining();
			int bufSize = Math.min(remaining, 4096 * 8);
			byte[] buffer = new byte[bufSize];
			while(remaining > 0){
				final int bytesToCopy = Math.min(remaining, buffer.length);
				src.get(buffer, 0, bytesToCopy);
				this.put(buffer, 0, bytesToCopy);
				remaining -= bytesToCopy;
			}
		}
	}

	@Override
	public void put(ByteBuffer src) {
		if(src.hasArray()) {
			Object array = src.array();
			final int bytesToCopy = src.remaining();
	        unsafe.copyMemory(array, byteArrayOffset + src.arrayOffset() + src.position(),
	        				  buffer, byteArrayOffset + offset + position,
                    		  bytesToCopy);
	        src.position(bytesToCopy+src.position());
	        this.position += bytesToCopy;
		} else {
			int remaining = (int)src.remaining();
			int bufSize = Math.min(remaining, 4096 * 8);
			byte[] buffer = new byte[bufSize];
			while(remaining > 0){
				final int bytesToCopy = Math.min(remaining, buffer.length);
				src.get(buffer, 0, bytesToCopy);
				this.put(buffer, 0, bytesToCopy);
				remaining -= bytesToCopy;
			}
		}
	}

	@Override
	public void destroy() {}

}
